﻿<!DOCTYPE html>
<html lang="zh">

<head>
  <meta charset="UTF-8" />
  <title>  💖  for  ZW </title>
  <link rel="shortcut icon" href="./favicon.ico" />
  <link rel="stylesheet" href="./css/style.css" />
</head>

<body>
  <script src="./js/three.min.js"></script>
  <!-- <script src='./js/MeshSurfaceSampler.js'></script> -->
  <script src="./js/TrackballControls.js"></script>
  <script src="./js/simplex-noise.js"></script>
  <script src="./js/OBJLoader.js"></script>
  <script src="./js/gsap.min.js"></script>
  <script src="./js/script.js"></script>

  <script>
    ; (function () {
      const _face = new THREE.Triangle()

      const _color = new THREE.Vector3()

      class MeshSurfaceSampler {
        constructor(mesh) {
          let geometry = mesh.geometry

          if (
            !geometry.isBufferGeometry ||
            geometry.attributes.position.itemSize !== 3
          ) {
            throw new Error(
              'THREE.MeshSurfaceSampler: Requires BufferGeometry triangle mesh.'
            )
          }

          if (geometry.index) {
            console.warn(
              'THREE.MeshSurfaceSampler: Converting geometry to non-indexed BufferGeometry.'
            )
            geometry = geometry.toNonIndexed()
          }

          this.geometry = geometry
          this.randomFunction = Math.random
          this.positionAttribute = this.geometry.getAttribute('position')
          this.colorAttribute = this.geometry.getAttribute('color')
          this.weightAttribute = null
          this.distribution = null
        }

        setWeightAttribute(name) {
          this.weightAttribute = name
            ? this.geometry.getAttribute(name)
            : null
          return this
        }

        build() {
          const positionAttribute = this.positionAttribute
          const weightAttribute = this.weightAttribute
          const faceWeights = new Float32Array(positionAttribute.count / 3) // Accumulate weights for each mesh face.

          for (let i = 0; i < positionAttribute.count; i += 3) {
            let faceWeight = 1

            if (weightAttribute) {
              faceWeight =
                weightAttribute.getX(i) +
                weightAttribute.getX(i + 1) +
                weightAttribute.getX(i + 2)
            }

            _face.a.fromBufferAttribute(positionAttribute, i)

            _face.b.fromBufferAttribute(positionAttribute, i + 1)

            _face.c.fromBufferAttribute(positionAttribute, i + 2)

            faceWeight *= _face.getArea()
            faceWeights[i / 3] = faceWeight
          } // Store cumulative total face weights in an array, where weight index
          // corresponds to face index.

          this.distribution = new Float32Array(positionAttribute.count / 3)
          let cumulativeTotal = 0

          for (let i = 0; i < faceWeights.length; i++) {
            cumulativeTotal += faceWeights[i]
            this.distribution[i] = cumulativeTotal
          }

          return this
        }

        setRandomGenerator(randomFunction) {
          this.randomFunction = randomFunction
          return this
        }

        sample(targetPosition, targetNormal, targetColor) {
          const cumulativeTotal =
            this.distribution[this.distribution.length - 1]
          const faceIndex = this.binarySearch(
            this.randomFunction() * cumulativeTotal
          )
          return this.sampleFace(
            faceIndex,
            targetPosition,
            targetNormal,
            targetColor
          )
        }

        binarySearch(x) {
          const dist = this.distribution
          let start = 0
          let end = dist.length - 1
          let index = -1

          while (start <= end) {
            const mid = Math.ceil((start + end) / 2)

            if (mid === 0 || (dist[mid - 1] <= x && dist[mid] > x)) {
              index = mid
              break
            } else if (x < dist[mid]) {
              end = mid - 1
            } else {
              start = mid + 1
            }
          }

          return index
        }

        sampleFace(faceIndex, targetPosition, targetNormal, targetColor) {
          let u = this.randomFunction()
          let v = this.randomFunction()

          if (u + v > 1) {
            u = 1 - u
            v = 1 - v
          }

          _face.a.fromBufferAttribute(this.positionAttribute, faceIndex * 3)

          _face.b.fromBufferAttribute(
            this.positionAttribute,
            faceIndex * 3 + 1
          )

          _face.c.fromBufferAttribute(
            this.positionAttribute,
            faceIndex * 3 + 2
          )

          targetPosition
            .set(0, 0, 0)
            .addScaledVector(_face.a, u)
            .addScaledVector(_face.b, v)
            .addScaledVector(_face.c, 1 - (u + v))

          if (targetNormal !== undefined) {
            _face.getNormal(targetNormal)
          }

          if (
            targetColor !== undefined &&
            this.colorAttribute !== undefined
          ) {
            _face.a.fromBufferAttribute(this.colorAttribute, faceIndex * 3)

            _face.b.fromBufferAttribute(
              this.colorAttribute,
              faceIndex * 3 + 1
            )

            _face.c.fromBufferAttribute(
              this.colorAttribute,
              faceIndex * 3 + 2
            )

            _color
              .set(0, 0, 0)
              .addScaledVector(_face.a, u)
              .addScaledVector(_face.b, v)
              .addScaledVector(_face.c, 1 - (u + v))

            targetColor.r = _color.x
            targetColor.g = _color.y
            targetColor.b = _color.z
          }
          return this
        }
      }

      THREE.MeshSurfaceSampler = MeshSurfaceSampler
    })()
  </script>
  <script>
      ; (function () {
        const _object_pattern = /^[og]\s*(.+)?/ // mtllib file_reference

        const _material_library_pattern = /^mtllib / // usemtl material_name

        const _material_use_pattern = /^usemtl / // usemap map_name

        const _map_use_pattern = /^usemap /

        const _vA = new THREE.Vector3()

        const _vB = new THREE.Vector3()

        const _vC = new THREE.Vector3()

        const _ab = new THREE.Vector3()

        const _cb = new THREE.Vector3()

        function ParserState() {
          const state = {
            objects: [],
            object: {},
            vertices: [],
            normals: [],
            colors: [],
            uvs: [],
            materials: {},
            materialLibraries: [],
            startObject: function (name, fromDeclaration) {
              // If the current object (initial from reset) is not from a g/o declaration in the parsed
              // file. We need to use it for the first parsed g/o to keep things in sync.
              if (this.object && this.object.fromDeclaration === false) {
                this.object.name = name
                this.object.fromDeclaration = fromDeclaration !== false
                return
              }

              const previousMaterial =
                this.object && typeof this.object.currentMaterial === 'function'
                  ? this.object.currentMaterial()
                  : undefined

              if (this.object && typeof this.object._finalize === 'function') {
                this.object._finalize(true)
              }

              this.object = {
                name: name || '',
                fromDeclaration: fromDeclaration !== false,
                geometry: {
                  vertices: [],
                  normals: [],
                  colors: [],
                  uvs: [],
                  hasUVIndices: false,
                },
                materials: [],
                smooth: true,
                startMaterial: function (name, libraries) {
                  const previous = this._finalize(false) // New usemtl declaration overwrites an inherited material, except if faces were declared
                  // after the material, then it must be preserved for proper MultiMaterial continuation.

                  if (
                    previous &&
                    (previous.inherited || previous.groupCount <= 0)
                  ) {
                    this.materials.splice(previous.index, 1)
                  }

                  const material = {
                    index: this.materials.length,
                    name: name || '',
                    mtllib:
                      Array.isArray(libraries) && libraries.length > 0
                        ? libraries[libraries.length - 1]
                        : '',
                    smooth:
                      previous !== undefined ? previous.smooth : this.smooth,
                    groupStart: previous !== undefined ? previous.groupEnd : 0,
                    groupEnd: -1,
                    groupCount: -1,
                    inherited: false,
                    clone: function (index) {
                      const cloned = {
                        index: typeof index === 'number' ? index : this.index,
                        name: this.name,
                        mtllib: this.mtllib,
                        smooth: this.smooth,
                        groupStart: 0,
                        groupEnd: -1,
                        groupCount: -1,
                        inherited: false,
                      }
                      cloned.clone = this.clone.bind(cloned)
                      return cloned
                    },
                  }
                  this.materials.push(material)
                  return material
                },
                currentMaterial: function () {
                  if (this.materials.length > 0) {
                    return this.materials[this.materials.length - 1]
                  }

                  return undefined
                },
                _finalize: function (end) {
                  const lastMultiMaterial = this.currentMaterial()

                  if (lastMultiMaterial && lastMultiMaterial.groupEnd === -1) {
                    lastMultiMaterial.groupEnd =
                      this.geometry.vertices.length / 3
                    lastMultiMaterial.groupCount =
                      lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart
                    lastMultiMaterial.inherited = false
                  } // Ignore objects tail materials if no face declarations followed them before a new o/g started.

                  if (end && this.materials.length > 1) {
                    for (let mi = this.materials.length - 1; mi >= 0; mi--) {
                      if (this.materials[mi].groupCount <= 0) {
                        this.materials.splice(mi, 1)
                      }
                    }
                  } // Guarantee at least one empty material, this makes the creation later more straight forward.

                  if (end && this.materials.length === 0) {
                    this.materials.push({
                      name: '',
                      smooth: this.smooth,
                    })
                  }

                  return lastMultiMaterial
                },
              } // Inherit previous objects material.
              // Spec tells us that a declared material must be set to all objects until a new material is declared.
              // If a usemtl declaration is encountered while this new object is being parsed, it will
              // overwrite the inherited material. Exception being that there was already face declarations
              // to the inherited material, then it will be preserved for proper MultiMaterial continuation.

              if (
                previousMaterial &&
                previousMaterial.name &&
                typeof previousMaterial.clone === 'function'
              ) {
                const declared = previousMaterial.clone(0)
                declared.inherited = true
                this.object.materials.push(declared)
              }

              this.objects.push(this.object)
            },
            finalize: function () {
              if (this.object && typeof this.object._finalize === 'function') {
                this.object._finalize(true)
              }
            },
            parseVertexIndex: function (value, len) {
              const index = parseInt(value, 10)
              return (index >= 0 ? index - 1 : index + len / 3) * 3
            },
            parseNormalIndex: function (value, len) {
              const index = parseInt(value, 10)
              return (index >= 0 ? index - 1 : index + len / 3) * 3
            },
            parseUVIndex: function (value, len) {
              const index = parseInt(value, 10)
              return (index >= 0 ? index - 1 : index + len / 2) * 2
            },
            addVertex: function (a, b, c) {
              const src = this.vertices
              const dst = this.object.geometry.vertices
              dst.push(src[a + 0], src[a + 1], src[a + 2])
              dst.push(src[b + 0], src[b + 1], src[b + 2])
              dst.push(src[c + 0], src[c + 1], src[c + 2])
            },
            addVertexPoint: function (a) {
              const src = this.vertices
              const dst = this.object.geometry.vertices
              dst.push(src[a + 0], src[a + 1], src[a + 2])
            },
            addVertexLine: function (a) {
              const src = this.vertices
              const dst = this.object.geometry.vertices
              dst.push(src[a + 0], src[a + 1], src[a + 2])
            },
            addNormal: function (a, b, c) {
              const src = this.normals
              const dst = this.object.geometry.normals
              dst.push(src[a + 0], src[a + 1], src[a + 2])
              dst.push(src[b + 0], src[b + 1], src[b + 2])
              dst.push(src[c + 0], src[c + 1], src[c + 2])
            },
            addFaceNormal: function (a, b, c) {
              const src = this.vertices
              const dst = this.object.geometry.normals

              _vA.fromArray(src, a)

              _vB.fromArray(src, b)

              _vC.fromArray(src, c)

              _cb.subVectors(_vC, _vB)

              _ab.subVectors(_vA, _vB)

              _cb.cross(_ab)

              _cb.normalize()

              dst.push(_cb.x, _cb.y, _cb.z)
              dst.push(_cb.x, _cb.y, _cb.z)
              dst.push(_cb.x, _cb.y, _cb.z)
            },
            addColor: function (a, b, c) {
              const src = this.colors
              const dst = this.object.geometry.colors
              if (src[a] !== undefined)
                dst.push(src[a + 0], src[a + 1], src[a + 2])
              if (src[b] !== undefined)
                dst.push(src[b + 0], src[b + 1], src[b + 2])
              if (src[c] !== undefined)
                dst.push(src[c + 0], src[c + 1], src[c + 2])
            },
            addUV: function (a, b, c) {
              const src = this.uvs
              const dst = this.object.geometry.uvs
              dst.push(src[a + 0], src[a + 1])
              dst.push(src[b + 0], src[b + 1])
              dst.push(src[c + 0], src[c + 1])
            },
            addDefaultUV: function () {
              const dst = this.object.geometry.uvs
              dst.push(0, 0)
              dst.push(0, 0)
              dst.push(0, 0)
            },
            addUVLine: function (a) {
              const src = this.uvs
              const dst = this.object.geometry.uvs
              dst.push(src[a + 0], src[a + 1])
            },
            addFace: function (a, b, c, ua, ub, uc, na, nb, nc) {
              const vLen = this.vertices.length
              let ia = this.parseVertexIndex(a, vLen)
              let ib = this.parseVertexIndex(b, vLen)
              let ic = this.parseVertexIndex(c, vLen)
              this.addVertex(ia, ib, ic)
              this.addColor(ia, ib, ic) // normals

              if (na !== undefined && na !== '') {
                const nLen = this.normals.length
                ia = this.parseNormalIndex(na, nLen)
                ib = this.parseNormalIndex(nb, nLen)
                ic = this.parseNormalIndex(nc, nLen)
                this.addNormal(ia, ib, ic)
              } else {
                this.addFaceNormal(ia, ib, ic)
              } // uvs

              if (ua !== undefined && ua !== '') {
                const uvLen = this.uvs.length
                ia = this.parseUVIndex(ua, uvLen)
                ib = this.parseUVIndex(ub, uvLen)
                ic = this.parseUVIndex(uc, uvLen)
                this.addUV(ia, ib, ic)
                this.object.geometry.hasUVIndices = true
              } else {
                // add placeholder values (for inconsistent face definitions)
                this.addDefaultUV()
              }
            },
            addPointGeometry: function (vertices) {
              this.object.geometry.type = 'Points'
              const vLen = this.vertices.length

              for (let vi = 0, l = vertices.length; vi < l; vi++) {
                const index = this.parseVertexIndex(vertices[vi], vLen)
                this.addVertexPoint(index)
                this.addColor(index)
              }
            },
            addLineGeometry: function (vertices, uvs) {
              this.object.geometry.type = 'Line'
              const vLen = this.vertices.length
              const uvLen = this.uvs.length

              for (let vi = 0, l = vertices.length; vi < l; vi++) {
                this.addVertexLine(this.parseVertexIndex(vertices[vi], vLen))
              }

              for (let uvi = 0, l = uvs.length; uvi < l; uvi++) {
                this.addUVLine(this.parseUVIndex(uvs[uvi], uvLen))
              }
            },
          }
          state.startObject('', false)
          return state
        } //

        class OBJLoader extends THREE.Loader {
          constructor(manager) {
            super(manager)
            this.materials = null
          }

          load(url, onLoad, onProgress, onError) {
            const scope = this
            const loader = new THREE.FileLoader(this.manager)
            loader.setPath(this.path)
            loader.setRequestHeader(this.requestHeader)
            loader.setWithCredentials(this.withCredentials)
            loader.load(
              url,
              function (text) {
                try {
                  onLoad(scope.parse(text))
                } catch (e) {
                  if (onError) {
                    onError(e)
                  } else {
                    console.error(e)
                  }

                  scope.manager.itemError(url)
                }
              },
              onProgress,
              onError
            )
          }

          setMaterials(materials) {
            this.materials = materials
            return this
          }

          parse(text) {
            const state = new ParserState()

            if (text.indexOf('\r\n') !== -1) {
              // This is faster than String.split with regex that splits on both
              text = text.replace(/\r\n/g, '\n')
            }

            if (text.indexOf('\\\n') !== -1) {
              // join lines separated by a line continuation character (\)
              text = text.replace(/\\\n/g, '')
            }

            const lines = text.split('\n')
            let line = '',
              lineFirstChar = ''
            let lineLength = 0
            let result = [] // Faster to just trim left side of the line. Use if available.

            const trimLeft = typeof ''.trimLeft === 'function'

            for (let i = 0, l = lines.length; i < l; i++) {
              line = lines[i]
              line = trimLeft ? line.trimLeft() : line.trim()
              lineLength = line.length
              if (lineLength === 0) continue
              lineFirstChar = line.charAt(0) // @todo invoke passed in handler if any

              if (lineFirstChar === '#') continue

              if (lineFirstChar === 'v') {
                const data = line.split(/\s+/)

                switch (data[0]) {
                  case 'v':
                    state.vertices.push(
                      parseFloat(data[1]),
                      parseFloat(data[2]),
                      parseFloat(data[3])
                    )

                    if (data.length >= 7) {
                      state.colors.push(
                        parseFloat(data[4]),
                        parseFloat(data[5]),
                        parseFloat(data[6])
                      )
                    } else {
                      // if no colors are defined, add placeholders so color and vertex indices match
                      state.colors.push(undefined, undefined, undefined)
                    }

                    break

                  case 'vn':
                    state.normals.push(
                      parseFloat(data[1]),
                      parseFloat(data[2]),
                      parseFloat(data[3])
                    )
                    break

                  case 'vt':
                    state.uvs.push(parseFloat(data[1]), parseFloat(data[2]))
                    break
                }
              } else if (lineFirstChar === 'f') {
                const lineData = line.substr(1).trim()
                const vertexData = lineData.split(/\s+/)
                const faceVertices = [] // Parse the face vertex data into an easy to work with format

                for (let j = 0, jl = vertexData.length; j < jl; j++) {
                  const vertex = vertexData[j]

                  if (vertex.length > 0) {
                    const vertexParts = vertex.split('/')
                    faceVertices.push(vertexParts)
                  }
                } // Draw an edge between the first vertex and all subsequent vertices to form an n-gon

                const v1 = faceVertices[0]

                for (let j = 1, jl = faceVertices.length - 1; j < jl; j++) {
                  const v2 = faceVertices[j]
                  const v3 = faceVertices[j + 1]
                  state.addFace(
                    v1[0],
                    v2[0],
                    v3[0],
                    v1[1],
                    v2[1],
                    v3[1],
                    v1[2],
                    v2[2],
                    v3[2]
                  )
                }
              } else if (lineFirstChar === 'l') {
                const lineParts = line.substring(1).trim().split(' ')
                let lineVertices = []
                const lineUVs = []

                if (line.indexOf('/') === -1) {
                  lineVertices = lineParts
                } else {
                  for (let li = 0, llen = lineParts.length; li < llen; li++) {
                    const parts = lineParts[li].split('/')
                    if (parts[0] !== '') lineVertices.push(parts[0])
                    if (parts[1] !== '') lineUVs.push(parts[1])
                  }
                }

                state.addLineGeometry(lineVertices, lineUVs)
              } else if (lineFirstChar === 'p') {
                const lineData = line.substr(1).trim()
                const pointData = lineData.split(' ')
                state.addPointGeometry(pointData)
              } else if ((result = _object_pattern.exec(line)) !== null) {
                // o object_name
                // or
                // g group_name
                // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869
                // let name = result[ 0 ].substr( 1 ).trim();
                const name = (' ' + result[0].substr(1).trim()).substr(1)
                state.startObject(name)
              } else if (_material_use_pattern.test(line)) {
                // material
                state.object.startMaterial(
                  line.substring(7).trim(),
                  state.materialLibraries
                )
              } else if (_material_library_pattern.test(line)) {
                // mtl file
                state.materialLibraries.push(line.substring(7).trim())
              } else if (_map_use_pattern.test(line)) {
                // the line is parsed but ignored since the loader assumes textures are defined MTL files
                // (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method)
                console.warn(
                  'THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.'
                )
              } else if (lineFirstChar === 's') {
                result = line.split(' ') // smooth shading
                // @todo Handle files that have varying smooth values for a set of faces inside one geometry,
                // but does not define a usemtl for each face set.
                // This should be detected and a dummy material created (later MultiMaterial and geometry groups).
                // This requires some care to not create extra material on each smooth value for "normal" obj files.
                // where explicit usemtl defines geometry groups.
                // Example asset: examples/models/obj/cerberus/Cerberus.obj

                /*
                 * http://paulbourke.net/dataformats/obj/
                 * or
                 * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf
                 *
                 * From chapter "Grouping" Syntax explanation "s group_number":
                 * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off.
                 * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form
                 * surfaces, smoothing groups are either turned on or off; there is no difference between values greater
                 * than 0."
                 */

                if (result.length > 1) {
                  const value = result[1].trim().toLowerCase()
                  state.object.smooth = value !== '0' && value !== 'off'
                } else {
                  // ZBrush can produce "s" lines #11707
                  state.object.smooth = true
                }

                const material = state.object.currentMaterial()
                if (material) material.smooth = state.object.smooth
              } else {
                // Handle null terminated files without exception
                if (line === '\0') continue
                console.warn('THREE.OBJLoader: Unexpected line: "' + line + '"')
              }
            }

            state.finalize()
            const container = new THREE.Group()
            container.materialLibraries = [].concat(state.materialLibraries)
            const hasPrimitives = !(
              state.objects.length === 1 &&
              state.objects[0].geometry.vertices.length === 0
            )

            if (hasPrimitives === true) {
              for (let i = 0, l = state.objects.length; i < l; i++) {
                const object = state.objects[i]
                const geometry = object.geometry
                const materials = object.materials
                const isLine = geometry.type === 'Line'
                const isPoints = geometry.type === 'Points'
                let hasVertexColors = false // Skip o/g line declarations that did not follow with any faces

                if (geometry.vertices.length === 0) continue
                const buffergeometry = new THREE.BufferGeometry()
                buffergeometry.setAttribute(
                  'position',
                  new THREE.Float32BufferAttribute(geometry.vertices, 3)
                )

                if (geometry.normals.length > 0) {
                  buffergeometry.setAttribute(
                    'normal',
                    new THREE.Float32BufferAttribute(geometry.normals, 3)
                  )
                }

                if (geometry.colors.length > 0) {
                  hasVertexColors = true
                  buffergeometry.setAttribute(
                    'color',
                    new THREE.Float32BufferAttribute(geometry.colors, 3)
                  )
                }

                if (geometry.hasUVIndices === true) {
                  buffergeometry.setAttribute(
                    'uv',
                    new THREE.Float32BufferAttribute(geometry.uvs, 2)
                  )
                } // Create materials

                const createdMaterials = []

                for (let mi = 0, miLen = materials.length; mi < miLen; mi++) {
                  const sourceMaterial = materials[mi]
                  const materialHash =
                    sourceMaterial.name +
                    '_' +
                    sourceMaterial.smooth +
                    '_' +
                    hasVertexColors
                  let material = state.materials[materialHash]

                  if (this.materials !== null) {
                    material = this.materials.create(sourceMaterial.name) // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.

                    if (
                      isLine &&
                      material &&
                      !(material instanceof THREE.LineBasicMaterial)
                    ) {
                      const materialLine = new THREE.LineBasicMaterial()
                      THREE.Material.prototype.copy.call(materialLine, material)
                      materialLine.color.copy(material.color)
                      material = materialLine
                    } else if (
                      isPoints &&
                      material &&
                      !(material instanceof THREE.PointsMaterial)
                    ) {
                      const materialPoints = new THREE.PointsMaterial({
                        size: 10,
                        sizeAttenuation: false,
                      })
                      THREE.Material.prototype.copy.call(
                        materialPoints,
                        material
                      )
                      materialPoints.color.copy(material.color)
                      materialPoints.map = material.map
                      material = materialPoints
                    }
                  }

                  if (material === undefined) {
                    if (isLine) {
                      material = new THREE.LineBasicMaterial()
                    } else if (isPoints) {
                      material = new THREE.PointsMaterial({
                        size: 1,
                        sizeAttenuation: false,
                      })
                    } else {
                      material = new THREE.MeshPhongMaterial()
                    }

                    material.name = sourceMaterial.name
                    material.flatShading = sourceMaterial.smooth ? false : true
                    material.vertexColors = hasVertexColors
                    state.materials[materialHash] = material
                  }

                  createdMaterials.push(material)
                } // Create mesh

                let mesh

                if (createdMaterials.length > 1) {
                  for (let mi = 0, miLen = materials.length; mi < miLen; mi++) {
                    const sourceMaterial = materials[mi]
                    buffergeometry.addGroup(
                      sourceMaterial.groupStart,
                      sourceMaterial.groupCount,
                      mi
                    )
                  }

                  if (isLine) {
                    mesh = new THREE.LineSegments(
                      buffergeometry,
                      createdMaterials
                    )
                  } else if (isPoints) {
                    mesh = new THREE.Points(buffergeometry, createdMaterials)
                  } else {
                    mesh = new THREE.Mesh(buffergeometry, createdMaterials)
                  }
                } else {
                  if (isLine) {
                    mesh = new THREE.LineSegments(
                      buffergeometry,
                      createdMaterials[0]
                    )
                  } else if (isPoints) {
                    mesh = new THREE.Points(buffergeometry, createdMaterials[0])
                  } else {
                    mesh = new THREE.Mesh(buffergeometry, createdMaterials[0])
                  }
                }

                mesh.name = object.name
                container.add(mesh)
              }
            } else {
              // if there is only the default parser state object with no geometry data, interpret data as point cloud
              if (state.vertices.length > 0) {
                const material = new THREE.PointsMaterial({
                  size: 1,
                  sizeAttenuation: false,
                })
                const buffergeometry = new THREE.BufferGeometry()
                buffergeometry.setAttribute(
                  'position',
                  new THREE.Float32BufferAttribute(state.vertices, 3)
                )

                if (state.colors.length > 0 && state.colors[0] !== undefined) {
                  buffergeometry.setAttribute(
                    'color',
                    new THREE.Float32BufferAttribute(state.colors, 3)
                  )
                  material.vertexColors = true
                }

                const points = new THREE.Points(buffergeometry, material)
                container.add(points)
              }
            }

            return container
          }
        }

        THREE.OBJLoader = OBJLoader
      })()
  </script>
</body>

</html>